import ConfigParser
import re
from .. import Enzymes, Modifications, Elements, Aminoacids
#Enzymes = ConfigParser.ConfigParser()
#Enzymes.read("xlh/config/enzymes.ini")

class Peptide:
	""" class for single peptides
	"""
	def __init__(self, sequence = ''):
		self._sequence = sequence
		self._positions = []
		self._modifications = {}
		self._hasXMod = False
	def setSequence(self, sequence):
		""" set peptide sequence
		"""
		self._sequence = sequence.upper().strip()
	def setStartPositions(self, list):
		""" set peptide start position list on 
		a protein
		"""
		self._positions = list
	def addStartPosition(self, start):
		""" add a single start position to defined list
		"""
		self._positions.append(int(start))
	def setModifications(self, modification):
		""" set peptide modifications as OMSSAMatch dictionary
		"""
		self._modifications = modification
	def addModification(self, pos, mod):
		""" add a modification to modifications dictionary
		Note: overrides existing modifications in given position
		"""
		#should check that pos and mod are valid
		if pos < 1 or pos > len(self._sequence):
			raise IndexError, "Index out of peptide sequence"
		else:
			self._modifications[pos] = mod
	def modifications(self):
		""" returns modification dictionary
		"""
		return self._modifications
	def sequence(self):
		""" return peptide sequence
		"""
		return self._sequence
	def startPositions(self):
		""" return peptide start positions list
		"""
		return self._positions
	def length(self):
		""" return peptide sequence length
		"""
		return len(self._sequence)
	def endPositions(self):
		""" return peptide end positions list
		"""
		return map(lambda x: x + self.length(), self._positions)
	def isNTerminal(self, Protein = None):
		""" return True if peptide is N-terminal on protein
		"""
		try:
			p = Protein.sequence().find(self._sequence)
			if p == 0:
				return True
		except AttributeError:
			if 0 in self._positions:
				return True
		
		return False
	def isCTerminal(self, Protein = None):
		""" return True if peptide is C-terminal on protein
		"""
		try:
			p = Protein.sequence().find(self._sequence)
			if p == len(Protein.sequence()) - len(self._sequence):
				return True
		except AttributeError:
			return False
		
		return False
	def bMass(self, ionIndex = 2):
		""" return the mass of b-ion with given ionIndex
		"""
		
		if ionIndex > self.length():
			ionIndex = self.length()
		
		# check if modification in 1st aminoacid (if one) is naa or np type
		# in case set initial ion mass to mod mass
		# set it to H mass otherwise
		
		try:
			if Modifications.get(self.modifications()[1], "modtype") == "np" or Modifications.get(self.modifications()[1], "modtype") ==  "naa":
				mass = Modifications.getfloat(self.modifications()[1], "mmass")
			else:
				mass = Elements.getfloat("H", "mmass")
		except KeyError:
			mass = Elements.getfloat("H", "mmass")
		
		
		# now add all aminoacid masses plus their modification mass (if one)
		for p in range(ionIndex):
			mass += Aminoacids.getfloat(self.sequence()[p], "mmass")
			
			try:
				# modifications indeces are list indeces plus one...
				mass += Modifications.getfloat(self.modifications()[p + 1], "mmass")
			except KeyError:
				continue
		
		return mass
	def yMass(self, ionIndex = 1):
		""" return the mass of y-ion with given ionIndex
		"""
		if ionIndex > self.length():
			ionIndex = self.length()
			
		mass = 0.0
#		cterm = Elements.getfloat("H", "mmass") + Elements.getfloat("C", "mmass") + 2 * Elements.getfloat("O", "mmass")
		cterm = 3 * Elements.getfloat("H", "mmass") - Elements.getfloat("e", "mmass") + Elements.getfloat("O", "mmass")
		
		# y indeces counts from right to left...
		ionIndex = self.length() - ionIndex
		
		# now add all aminoacid masses plus their modification mass (if one)
		for p in range(ionIndex, self.length()):
			mass += Aminoacids.getfloat(self.sequence()[p], "mmass")
			
			try:
				# modifications indeces are list indeces plus one...
				mass += Modifications.getfloat(self.modifications()[p + 1], "mmass")
			except KeyError:
				continue
		
		# check if modification in last aminoacid (if one) is caa or cp type
		# in case set initial ion mass to mod mass
		# set it to cterm mass otherwise
		
		try:
			if Modifications.get(self.modifications()[1], "modtype") == "cp" or Modifications.get(self.modifications()[1], "modtype") ==  "caa":
				mass += Modifications.getfloat(self.modifications()[1], "mmass")
			else:
				mass += cterm
		except KeyError:
			mass += cterm

				
		return mass
	def aMass(self, ionIndex = 2):
		""" return the mass of a-ion with given ionIndex
		"""
		return self.bMass(ionIndex) - 27.9949
	def setXMod(self, b):
		self._hasXMod = bool(b)
	def hasXMod(self):
		return self._hasXMod
	def molecularMass(self):
		return self.yMass(0) + self.bMass(self.length()) - 2 * Elements.getfloat("H", "mmass")
	def gravy(self):
		""" return peptide hydropathy as described in
		Kyte J., Doolittle R.F. Reference: J. Mol. Biol. 157:105-132(1982).   
		"""
		return sum(map(lambda x: Aminoacids.getfloat(x, "gravy"), self.sequence()))/self.length()
	
		
class Protein:
	""" This class defines a protein sequence
	"""
	def __init__(self, definitionTuple = ('','','')):
		""" initialize the protein, possibly with a tuple
		of accession, description, sequence
		as from xlh.readEntryFromNCBI()
		"""
		self._accession = definitionTuple[0]
		self._description = definitionTuple[1]
		self._sequence = definitionTuple[2]
		self._peptides = []
	def setSequence(self, sequence):
		""" set protein sequence
		"""
		self._sequence = sequence.upper().strip()
	def addSequence(self, sequence):
		""" add a sequence to previous string, useful
		when reading multiline sequencese
		"""
		self._sequence += sequence.upper().strip()
	def setAccession(self, accession):
		""" set protein accession number
		"""
		self._accession = accession
	def setDescription(self, description):
		""" set protein description
		"""
		self._description = description
	def sequence(self):
		""" return protein sequence as string
		"""
		return self._sequence
	def accession(self):
		""" return protein accession
		"""
		return self._accession
	def description(self):
		""" return protein description
		"""
		return self._description
	def length(self):
		""" return protein sequence length
		"""
		return len(self._sequence)
	def cleave(self, enzyme = "Trypsin", missed = 1):
		""" cleaves protein with given enzyme (default Trypsin)
		and missed cleavages (default 1). Doesn't return anything
		but fills the peptide list, attribute of this class
		"""
		# reset peptides...
		self._peptides = []
		if missed < 0:
			missed = 0
		
		pd = {} # peptide dictionary, to build the peptide set
		
		# set the first element of cleave to 0
		# so we are sure that the first peptide
		# is recognized later on
		
		cleavePositions = {0:1}
		
		# get the enzyme regular expression from
		# enzymes.ini
		regexp = Enzymes.get(enzyme, "expr")
		
		# iterate over the sequence to get all occourrences of
		# cleavage sites
		
		for i in range(len(self._sequence)):
			try:
				t = re.search(regexp, self._sequence[i:]).span()
				cleavePositions[1 + t[0]+i] = 1
			except AttributeError:
				continue
		
		#convert hash to array and sort
		cleavePositions = cleavePositions.keys()
		cleavePositions.sort()
		# add the last aminoacid that has been excluded...
		cleavePositions.append(len(self._sequence))

		# get the peptides as subsequences of protein sequence
		# allowing a jump according to the number of missed cleavages
		
		for m in range(missed + 1):
			for p in range(len(cleavePositions)):
				try:
					np = self._sequence[cleavePositions[p]:cleavePositions[1+p+m]]
				except IndexError:
					continue
				
				try:
					pd[np].append(cleavePositions[p])
				except KeyError:
					pd[np] = [cleavePositions[p]]
		# now from pd we build a set of peptides
		for p in pd.keys():
			np = Peptide()
			np.setSequence(p)
			np.setStartPositions(pd[p])
			self._peptides.append(np)

		pd.clear()
			
	def peptides(self):
		""" return peptide objects
		"""
		return self._peptides
	def items(self):
		""" return an iterator over peptides
		"""
		return iter(self.peptides())
	def peptideSequences(self):
		""" return peptide sequences
		"""
		return map(lambda x: x.sequence(), self.peptides())
	def gravy(self):
		""" return peptide hydropathy as described in
		Kyte J., Doolittle R.F. Reference: J. Mol. Biol. 157:105-132(1982).   
		"""
		return sum(map(lambda x: Aminoacids.getfloat(x, "gravy"), self.sequence()))/self.length()
	def subsequence(self, start = 1, center = 0, span = 15):
		""" returns a peptide instance that spans a number of aminoacids
		from a starting position or centered on a specific aa
		position are given as aminoacid number
		"""
		t = Peptide()
		if center:
			start = center - span / 2
		
		start -= 1		# remember that python strings are indexed starting from 0
		if start < 0:
			# if we get lower than 0... start from 0
			start = 0

		end = start + span


		if end > self.length():
			end = self.length()

		t.setSequence(self.sequence()[start:end])
		return t
				
